package com.tuyrt.hi.nav.compiler

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.google.auto.service.AutoService
import com.tuyrt.hi.nav.annotation.Destination
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
import java.util.*
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic
import javax.tools.StandardLocation
import kotlin.collections.HashMap


@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.tuyrt.hi.nav.annotation.Destination")
class NavProcessor : AbstractProcessor() {

    private val PAGE_TYPE_ACTIVITY = "Activity"
    private val PAGE_TYPE_FRAGMENT = "Fragment"
    private val PAGE_TYPE_DIALOG = "Dialog"
    private val OUTPUT_FILE_NAME = "destination.json"

    private val ACTIVITY = "androidx.appcompat.app.AppCompatActivity"
    private val FRAGMENT = "androidx.fragment.app.Fragment"
    private val DIALOG = "androidx.fragment.app.DialogFragment"

    private lateinit var messager: Messager
    private lateinit var filer: Filer

    private lateinit var types: Types
    private lateinit var elementUtils: Elements
    private lateinit var type_Activity: TypeMirror
    private lateinit var type_Dialog: TypeMirror
    private lateinit var type_Fragment: TypeMirror


    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        messager = processingEnv!!.messager
        messager.printMessage(Diagnostic.Kind.NOTE, "enter init....")
        filer = processingEnv.filer

        types = processingEnv.typeUtils
        elementUtils = processingEnv.elementUtils

        /**
         *  用于判断类型是 Activity or Fragment or Dialog
         */
        type_Activity = elementUtils.getTypeElement(ACTIVITY).asType()
        type_Dialog = elementUtils.getTypeElement(DIALOG).asType()
        type_Fragment = elementUtils.getTypeElement(FRAGMENT).asType()

        messager.printMessage(Diagnostic.Kind.NOTE, "add types elementUtils....")
    }

    override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
        val elements = roundEnv?.getElementsAnnotatedWith(Destination::class.java)
        if (!elements.isNullOrEmpty()) {
            val destMap = HashMap<String, JSONObject>()
            handleDestination(elements, Destination::class.java, destMap)
            try {

                val resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME)
                // /app/build/intermediates/javac/debug/classes/目录下
                //app/main/assets/
                val resourcePath = resource.toUri().path

                val appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4)
                val assestPath = appPath + "src/main/assets"

                val file = File(assestPath)
                if (file.exists()) {
                    file.mkdirs()
                }
                val content = JSON.toJSONString(destMap)

                val outputFile = File(assestPath, OUTPUT_FILE_NAME)
                if (outputFile.exists()) {
                    outputFile.delete()
                }
                outputFile.createNewFile()
                val fileOutputStream = FileOutputStream(outputFile)
                val writer = OutputStreamWriter(fileOutputStream)
                writer.write(content)
                writer.flush()

                fileOutputStream.close()
                writer.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return false
    }

    private fun handleDestination(elements: Set<Element>, destinationClazz: Class<Destination>, destMap: HashMap<String, JSONObject>) {
        elements.forEach { element ->
            // Destination注解 标记在类上，所以 elements实际上是TypeElement的集合
            val typeElement = element as TypeElement

            //全类名
            val clazzName = typeElement.qualifiedName.toString()

            val annotation: Destination = typeElement.getAnnotation(destinationClazz)
            val pageUrl: String = annotation.pageUrl
            val asStarter: Boolean = annotation.asStarter
            val id = Math.abs(clazzName.hashCode())

            // 获取注解标记的类型 ：Activity,Dialog,Fragment
            val destType = getDestinationType2(typeElement)

            if (destMap.containsKey(pageUrl)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "不同的页面不允许使用相同的pageUrl:$pageUrl")
            } else {
                val jsonObject = JSONObject()
                jsonObject["clazzName"] = clazzName
                jsonObject["pageUrl"] = pageUrl
                jsonObject["asStarter"] = asStarter
                jsonObject["id"] = id
                jsonObject["destType"] = destType
                destMap[pageUrl] = jsonObject
            }
        }
    }

    private fun getDestinationType(typeElement: TypeElement): String {
        val typeMirror = typeElement.superclass
        //androidx.fragment.app.Fragment
        val superClazzName = typeMirror.toString()
        messager.printMessage(Diagnostic.Kind.NOTE, "Type superClazzName:$superClazzName")
        if (superClazzName.endsWith("AppCompatActivity")) {
            messager.printMessage(Diagnostic.Kind.NOTE, "AppCompatActivity Type activityType:$superClazzName")
            return PAGE_TYPE_ACTIVITY.lowercase(Locale.getDefault())
        } else if (superClazzName.endsWith("DialogFragment")) {
            return PAGE_TYPE_DIALOG.lowercase(Locale.getDefault())
        } else if (superClazzName.endsWith("Fragment")) {
            return PAGE_TYPE_FRAGMENT.lowercase(Locale.getDefault())
        }

        //这个父类的类型是类的类型，或者是接口的类型
        if (typeMirror is DeclaredType) {
            val element = typeMirror.asElement()
            if (element is TypeElement) {
                return getDestinationType(element)
            }
        }
        return ""
    }

    /**
     *  根据 element，判断其class类型
     */
    private fun getDestinationType2(typeElement: TypeElement): String {
        val tm = typeElement.asType()
        return when {
            types.isSubtype(tm, type_Activity) -> {
                return PAGE_TYPE_ACTIVITY.lowercase(Locale.getDefault())
            }
            // **** 由于DialogFragment继承自Fragment，所以优先判断是否是DialogFragment
            types.isSubtype(tm, type_Dialog) -> {
                return PAGE_TYPE_DIALOG.lowercase(Locale.getDefault())
            }
            types.isSubtype(tm, type_Fragment) -> {
                return PAGE_TYPE_FRAGMENT.lowercase(Locale.getDefault())
            }
            else -> ""
        }
    }
}